/* Built in part using code from the following: 
 *  
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.neuronrobotics.replicator.gui.preview.android;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import com.neuronrobotics.opengl.scene.ColorsUtility;
import com.neuronrobotics.opengl.scene.GLHandles;
import com.neuronrobotics.opengl.scene.GroupNode;
import com.neuronrobotics.opengl.scene.SceneNode;
import com.neuronrobotics.opengl.scene.ArrayShapeNode;
import com.neuronrobotics.replicator.gui.preview.android.PickingRay.PickUtilities;
import com.neuronrobotics.replicator.gui.preview.controller.STLPreviewMouseController.MouseControlMode;
import com.neuronrobotics.replicator.gui.preview.model.STLWorkspaceModel;
import com.neuronrobotics.replicator.gui.preview.model.STLWorkspaceModel.PlacementStatus;
import com.neuronrobotics.replicator.gui.preview.model.STLWorkspaceModelListener;
import com.neuronrobotics.replicator.gui.stl.GeneralTransform3D;
import com.neuronrobotics.replicator.gui.stl.STLObject;
import com.neuronrobotics.replicator.gui.stl.STLWorkspaceObject;
import com.neuronrobotics.replicator.gui.stl.GeneralTransform3D.MatrixOrder;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;

public class AndroidWorkspaceRenderer implements GLSurfaceView.Renderer,
		STLWorkspaceModelListener {

	private GLHandles theGLHandles;

	private GroupNode rootNode;

	private final float[] mProjMatrix = new float[16];
	private final float[] mVMatrix = new float[16];

	public volatile float mAngle;

	private final float[] posLookUp = { 0, 150, 150, 0, 0, 0, 0, 1, 0 };

	private ArrayList<STLObject> theBaseSTLObjects;

	private ArrayList<STLTransformGroupNode> theSTLObjectTransformGroups;
	
	
	private ArrayList<AngleIndicatorGroup> angleIndicators;
	private ArrayList<PositionIndicatorGroup> positionIndicators;
	private ArrayList<ScaleIndicatorGroup> scaleIndicators;

	private STLWorkspaceObject theWorkspaceObject;

	private STLPreviewViewAndroid theView;

	private STLWorkspaceModel theModel;

	private float ratio = 1;
	private float width, height;
	private float fovy = 45;
	private float nearClippingPlaneDistance = 1;
	private float farClippingPlaneDistance = 400;
	private boolean modelLoaded = false;

	private int mProgram;
	private int vertexShader;
	private int fragmentShader;

	public AndroidWorkspaceRenderer(STLPreviewViewAndroid v) {
		super();
		theView = v;
		theGLHandles = new GLHandles();
	}

	public AndroidWorkspaceRenderer(STLPreviewViewAndroid v,
			STLWorkspaceModel stlwm) {
		this(v);
		this.setWorkspaceModel(stlwm);
	}

	@Override
	public void onSurfaceCreated(GL10 unused, EGLConfig config) {
		// place
		// Set the background frame color
			
		Log.d("SC", "Surface Created Called");

		GLES20.glClearColor(.25f, .25f, .25f, 1.0f);
		GLES20.glEnable(GLES20.GL_DEPTH_TEST);
		GLES20.glDepthFunc(GLES20.GL_LEQUAL);

		Matrix.setIdentityM(mVMatrix, 0);

		setupHandles();

		GLES20.glUseProgram(mProgram);

	}

	private void setupHandles() {

		vertexShader = ShaderUtility.loadShader(GLES20.GL_VERTEX_SHADER,
				ShaderUtility.perPixel_VertexShaderCode_OneColor);

		fragmentShader = ShaderUtility.loadShader(GLES20.GL_FRAGMENT_SHADER,
				ShaderUtility.perPixel_FragmentShaderCode_OneColor);

		mProgram = GLES20.glCreateProgram(); // create empty OpenGL Program
		GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader
														// to program
		GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment
															// shader to program

		GLES20.glLinkProgram(mProgram); // create OpenGL program executables

		this.theGLHandles.mProgram = mProgram;
		
		this.theGLHandles.lightsOnHandle = GLES20.glGetUniformLocation(mProgram, "lightsOn");
		checkGlError("glGetUniformLocation", "lightsOn");

		this.theGLHandles.mPositionHandle = GLES20.glGetAttribLocation(
				mProgram, "aPosition");
		checkGlError("glGetAttribLocation", "aPosition");

		this.theGLHandles.mNormalHandle = GLES20.glGetAttribLocation(mProgram,
				"aNormal");
		checkGlError("glGetAttribLocation", "aNormal");

		this.theGLHandles.mColorHandle = GLES20.glGetUniformLocation(mProgram,
				"aColor");
		checkGlError("glGetUniformLocation", "aColor");

		this.theGLHandles.mMMatrixHandle = GLES20.glGetUniformLocation(
				mProgram, "uMMatrix");
		checkGlError("glGetUniformLocation", "uMMatrix");

		this.theGLHandles.mVMatrixHandle = GLES20.glGetUniformLocation(
				mProgram, "uVMatrix");
		checkGlError("glGetUniformLocation", "uVMatrix");

		this.theGLHandles.mPMatrixHandle = GLES20.glGetUniformLocation(
				mProgram, "uPMatrix");
		checkGlError("glGetUniformLocation", "uPMatrix");

		this.theGLHandles.lightPosHandle = GLES20.glGetUniformLocation(
				mProgram, "uLightPos");
		checkGlError("glGetUniformLocation", "uLightPos");
	}

	public void setWorkspaceModel(STLWorkspaceModel stlwm) {

		rootNode = new GroupNode();

		if (theModel != null)
			theModel.removeListener(this);
		theModel = stlwm;
		theModel.addSTLWorkspaceModelListener(this);
		this.theSTLObjectTransformGroups = new ArrayList<STLTransformGroupNode>();
		this.theBaseSTLObjects = new ArrayList<STLObject>();
		this.angleIndicators = new ArrayList<AngleIndicatorGroup>();
		this.positionIndicators = new ArrayList<PositionIndicatorGroup>();
		this.scaleIndicators = new ArrayList<ScaleIndicatorGroup>();
		
		modelLoaded = false;

	}

	@Override
	public void onDrawFrame(GL10 unused) {

		if (!modelLoaded) {
			loadModel();
			modelLoaded = true;
		}

		Log.d("MyTag", "OnDrawFrame");

		// Draw background color
		GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

		// Set the camera position (View matrix)
		Matrix.setIdentityM(mVMatrix, 0);
		Matrix.setLookAtM(mVMatrix, 0, posLookUp[0], posLookUp[1],
				posLookUp[2], posLookUp[3], posLookUp[4], posLookUp[5],
				posLookUp[6], posLookUp[7], posLookUp[8]);
		Log.d("MyTag", Arrays.toString(mVMatrix));

		SceneNode.draw(rootNode, mVMatrix, mProjMatrix);
		
	}

	// This method should only be called on OpenGL thread
	private void loadModel() {

		Iterator<STLObject> stlIt = theModel.getBaseSTLObjectIterator();
		Iterator<float[]> transF = theModel.getTransformMatrixIterator();
		// theWorkspaceDrawable = null;
		theWorkspaceObject = null;

		while (stlIt.hasNext()) {
			STLObject currObj = stlIt.next();
			float[] currT = Arrays.copyOf(transF.next(), 16);

			addSTLToView(currObj, currT);
		}

		if (theModel.getWorkspaceObject() != null) {
			theWorkspaceObject = new STLWorkspaceObject(
					theModel.getWorkspaceObject());
			rootNode.addChild(STLShapeNodeFactory.getSTLWorkspaceShape(
					theWorkspaceObject, theGLHandles));
			// theWorkspaceDrawable = new
			// STLWorkspaceObjectDrawable(theWorkspaceObject);
		}

	}

	private void addSTLToView(STLObject currObj, float[] currT) {
		MatrixOrder.convertOrder(currT, 0, currT, 0);
		
		ArrayShapeNode newSTLShape = STLShapeNodeFactory.getSTLShape(currObj,
				theGLHandles);
		ArrayShapeNode newOutline = STLShapeNodeFactory.getSTLFacetOutline(currObj,
				theGLHandles);

		STLTransformGroupNode newTranGroup = new STLTransformGroupNode(currObj,
				newSTLShape, newOutline, rootNode);
		theSTLObjectTransformGroups.add(newTranGroup);
		int index = theSTLObjectTransformGroups.size()-1;		
		
		newSTLShape.setColor(ColorsUtility.green[0], ColorsUtility.green[1],ColorsUtility.green[2],ColorsUtility.green[3]);
		
		float[] center = new float[3];
		currObj.getCenter().get(center);
		float rad = Math.max(currObj.getXLength(),currObj.getZLength())/2.0f;
		
					
		AngleIndicatorGroup newCircle = new AngleIndicatorGroup(theGLHandles, center[0], center[1], center[2], rad);
		newCircle.setVisible(false);
		this.angleIndicators.add(newCircle);
		
		rootNode.addChild(newCircle);		
		
		PositionIndicatorGroup newPositionInd = new PositionIndicatorGroup(theGLHandles, new float[]{0,1,0}, center[0], center[2]);
		newPositionInd.setVisible(false);
		this.positionIndicators.add(newPositionInd);
		
		rootNode.addChild(newPositionInd);		
		
		float[] max = new float[3];
		float[] min = new float[3];
		currObj.getMax().get(max);
		currObj.getMin().get(min);		
		ScaleIndicatorGroup newScaleInd = new ScaleIndicatorGroup(theGLHandles, max, min);
		newScaleInd.setVisible(false);
		this.scaleIndicators.add(newScaleInd);
		
		rootNode.addChild(newScaleInd);		
		
		repositionAngleInidicator(currObj, index, currT);		

		newTranGroup.setTransform(currT, MatrixOrder.COLUMN_MAJOR);

		newOutline.setColor(1.0f, 1.0f, 1.0f, 1.0f);
		newOutline.setVisible(false);

		theBaseSTLObjects.add(currObj);
		theView.requestRender();
	}

	@Override
	public void onSurfaceChanged(GL10 unused, final int newWidth,
			final int newHeight) {

		width = (float) newWidth;
		height = (float) newHeight;

		GLES20.glViewport(0, 0, newWidth, newHeight);

		ratio = (float) width / height;

		// set projection matrix
		perspectiveM(mProjMatrix, 0, fovy, ratio, nearClippingPlaneDistance,
				farClippingPlaneDistance);

	}

	/**
	 * Define a projection matrix in terms of a field of view angle, an aspect
	 * ratio, and z clip planes
	 * 
	 * @param m
	 *            the float array that holds the perspective matrix
	 * @param offset
	 *            the offset into float array m where the perspective matrix
	 *            data is written
	 * @param fovy
	 *            field of view in y direction, in degrees
	 * @param aspect
	 *            width to height aspect ratio of the viewport
	 * @param zNear
	 * @param zFar
	 */
	public static void perspectiveM(float[] m, int offset, float fovy,
			float aspect, float zNear, float zFar) {
		float f = 1.0f / (float) Math.tan(fovy * (Math.PI / 360.0));
		float rangeReciprocal = 1.0f / (zNear - zFar);

		m[offset + 0] = f / aspect;
		m[offset + 1] = 0.0f;
		m[offset + 2] = 0.0f;
		m[offset + 3] = 0.0f;

		m[offset + 4] = 0.0f;
		m[offset + 5] = f;
		m[offset + 6] = 0.0f;
		m[offset + 7] = 0.0f;

		m[offset + 8] = 0.0f;
		m[offset + 9] = 0.0f;
		m[offset + 10] = (zFar + zNear) * rangeReciprocal;
		m[offset + 11] = -1.0f;

		m[offset + 12] = 0.0f;
		m[offset + 13] = 0.0f;
		m[offset + 14] = 2.0f * zFar * zNear * rangeReciprocal;
		m[offset + 15] = 0.0f;
	}

	public static void checkGlError(String glOperation, String name) {
		int error;
		while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
			Log.e("SC", glOperation + ": glError " + error);
			Log.e("SC", "Error looking for " + name);
			throw new RuntimeException(glOperation + ": glError " + error);
		}
	}

	public void setCamera(float[] position, float[] lookAt) {
		// NOTE: this doesn't actually set new camera transform
		// until there is a call to render the scene
		posLookUp[0] = (float) position[0];
		posLookUp[1] = (float) position[1];
		posLookUp[2] = (float) position[2];
		posLookUp[3] = (float) lookAt[0];
		posLookUp[4] = (float) lookAt[1];
		posLookUp[5] = (float) lookAt[2];
	}

	public void getCameraData(float[] position, float[] lookAt) {
		// NOTE: if setCamera method has been called but has not been
		// rendered, this may return seemingly incorrect data
		position[0] = posLookUp[0];
		position[1] = posLookUp[1];
		position[2] = posLookUp[2];
		lookAt[0] = posLookUp[3];
		lookAt[1] = posLookUp[4];
		lookAt[2] = posLookUp[5];
	}

	@Override
	public void alertSTLObjectAdded(final STLObject newSTL,
			final GeneralTransform3D transform) {

		theView.queueEvent(new Runnable() {

			@Override
			public void run() {

				theView.setLoading(true);
				float[] newTransform = new float[16];
				transform.get(newTransform, MatrixOrder.COLUMN_MAJOR);

				Log.d("OW",
						"Adding with this transform: "
								+ Arrays.toString(newTransform));

				float[] result = new float[3];
				theModel.getCurrMins(newSTL, result);
				Log.d("SG", "New Min: " + result[1]);

				addSTLToView(newSTL, newTransform);
				theView.requestRender();
				theView.setLoading(false);
			}

		});

	}

	@Override
	public void alertSTLObjectMoved(final STLObject stl,
			final GeneralTransform3D transform) {
		// This technically doesn't need to be on the GL thread
		// but this prevents events from firing out of order
		theView.queueEvent(new Runnable() {

			@Override
			public void run() {

				int index = theBaseSTLObjects.indexOf(stl);
				if (index == -1)
					return;
				float[] newTransform = new float[16];
				transform.get(newTransform, MatrixOrder.COLUMN_MAJOR);

				Log.d("OW",
						"Setting this transform: "
								+ Arrays.toString(newTransform));

				repositionAngleInidicator(stl, index, newTransform);
				
				float[] max = new float[3];
				float[] min = new float[3];				
				
				theModel.getCurrMaxes(stl, max);
				theModel.getCurrMins(stl, min);
				
				resizeScaleIndicator(index, max, min);
				adjustPositionIndicator(index, (max[0]+min[0])/2f, (max[2]+min[2])/2f);
								
				theSTLObjectTransformGroups.get(index).setTransform(
						newTransform, MatrixOrder.COLUMN_MAJOR);
				
				theView.requestRender();
			}
			
		});

	}
	
	private void resizeScaleIndicator(int index,float[] max,float[]min){
		this.scaleIndicators.get(index).getScaleBox().setMax(max[0], max[1], max[2]);
		this.scaleIndicators.get(index).getScaleBox().setMin(min[0], min[1], min[2]);
	}
	
	private void adjustPositionIndicator(int index,float x, float z){
		this.positionIndicators.get(index).setX(x);
		this.positionIndicators.get(index).setZ(z);
	}
	
	private void repositionAngleInidicator(final STLObject stl,
			int index, float[] newTransform) {
		
		AngleIndicatorGroup currAngleIndic = angleIndicators.get(index);
				
		float startRad = getStartAngle(newTransform);
		float endRad = getEndAngle(newTransform);
		currAngleIndic.setStartEndAngle(startRad, endRad);
				
		float[] circTransform = new float[16];
		Matrix.setIdentityM(circTransform, 0);
		float max[] = new float[3];
		float min[] = new float[3];
		
		theModel.getCurrMaxes(stl, max);
		theModel.getCurrMaxes(stl, min);
		
		
		circTransform = Arrays.copyOf(newTransform, 16);
		
		currAngleIndic.setTransform(circTransform , MatrixOrder.COLUMN_MAJOR);
	}

			
	private float getEndAngle(float[] newTransform) {	
		return 0;
	}


	private float getStartAngle(float[] newTransform) {
		//This works? I'm not sure why
		float[] vec = new float[]{1,0,0,0};
		Matrix.multiplyMV(vec, 0, newTransform, 0, vec, 0);
		float len = Matrix.length(vec[0], 0, vec[2]);
		
		vec[0]/=len;
		vec[1]=0;
		vec[2]/=len;
		
		float result = 0;
		
		float x = vec[0];
		float y = vec[2];
		float theta = (float)Math.atan(x/y);
		
		float epsilon = 0.001f;
		
		if(Math.abs(1-x)<=epsilon&&Math.abs(0-y)<=epsilon) return 0;
		
		if(x>0){
			if(y>0){
				//Q1 correct
				result = (float)((Math.PI/2.0)-theta);
				Log.d("CIRC","Q1");
			} else {
				//Q4
				result = (float)((3*Math.PI/2.0)-theta);
				Log.d("CIRC","Q4");
			}
		} else{
			if(y>0){
				//q2
				result = (float)((Math.PI/2.0)-theta);
				Log.d("CIRC","Q2");
			} else{
				//q3 correct
				result = (float)((3*Math.PI/2.0)-theta);
				Log.d("CIRC","Q3");
			}
		}
		
		Log.d("CIRC", "Result: "+result);
		
		return (float)(Math.PI*2-result);
	}

	@Override
	public void alertSTLObjectRemoved(final STLObject stl) {

		theView.queueEvent(new Runnable() {
			int index = theBaseSTLObjects.indexOf(stl);

			@Override
			public void run() {
				if (index != -1) {
					rootNode.removeChild(theSTLObjectTransformGroups
							.remove(index));
					theBaseSTLObjects.remove(index);
					rootNode.removeChild(angleIndicators.remove(index));
					rootNode.removeChild(positionIndicators.remove(index));
					rootNode.removeChild(scaleIndicators.remove(index));
					theView.requestRender();
				}

			}
		});

	}

	@Override
	public void alertSTLWorkspaceObjectChanged(
			final STLWorkspaceObject newWorkspace) {
		theView.queueEvent(new Runnable() {

			@Override
			public void run() {
				resetWorkObject(newWorkspace);
			}
		});

	}

	private void resetWorkObject(STLWorkspaceObject newWorkspace) {
		//Placeholder
	}

	@Override
	public void alertSTLPlacementStatusChanged(final STLObject stl,
			final PlacementStatus newStatus) {

		theView.queueEvent(new Runnable() {

			@Override
			public void run() {
				int index = theBaseSTLObjects.indexOf(stl);
				if (index == -1)
					return;
				float[] newColor = new float[4];

				switch (newStatus) {
				case VALID_PLACEMENT:
					newColor = ColorsUtility.green;
					break;
				case MODEL_COLLISION:
					newColor = ColorsUtility.red;
					break;
				case NOT_IN_WORKSPACE:
					newColor = ColorsUtility.blue;
					break;
				default:
					break;
				}

				theSTLObjectTransformGroups
						.get(index)
						.getTheShape()
						.setColor(newColor[0], newColor[1], newColor[2],
								newColor[3]);
			}
		});
	}

	public void clearPick() {
		int i = 0;
		for (STLTransformGroupNode stg : theSTLObjectTransformGroups){
			stg.getTheOutline().setVisible(false);
			angleIndicators.get(i).setVisible(false);
			positionIndicators.get(i).setVisible(false);
			scaleIndicators.get(i).setVisible(false);
			i++;
		}
	}

	public STLObject getPick(float xPick, float yPick, PickingRay mostRecentPickRay) {

		PickingRay theRay = new PickingRay();

		getPickingRay((float) xPick, height - (float) yPick, theRay);

		Log.d("Pick", "Projection: " + Arrays.toString(this.mProjMatrix));
		Log.d("Pick", "View: " + Arrays.toString(this.mVMatrix));
		Log.d("Pick", "Ratio: " + this.ratio);

		STLObject thePick = null;

		int ct = 0;

		for (STLObject stl : theBaseSTLObjects) {

			float radius = Math.max(stl.getZLength(),
					Math.max(stl.getXLength(), stl.getYLength())) / 2;

			float[] maxes = new float[3], mins = new float[3];
			theModel.getCurrMaxes(stl, maxes);
			theModel.getCurrMins(stl, mins);

			Point3f max = new Point3f(maxes), min = new Point3f(mins);
			Point3f centerd = new Point3f((max.x + min.x) / 2,
					(max.y + min.y) / 2, (max.z + min.z) / 2);
			Vector3f center = new Vector3f(centerd);

			Log.d("Pick", "STL: " + stl.getName());
			Log.d("Pick", "Center: " + center);
			Log.d("Pick", "Radius: " + radius);
			Log.d("Pick", "Click x,y " + xPick + "," + yPick);
			Log.d("Pick", "ClickPos " + theRay.getClickPosInWorld());
			Log.d("Pick", "Direction " + theRay.getDirection());

			boolean intersect = false;
			intersect = PickUtilities.raySphereIntersection(theRay.getClickPosInWorld(),
					theRay.getDirection(), center, radius);

			Log.d("Pick", "Intersect? " + intersect);

			if (intersect && thePick == null) {
				thePick = stl;
				theSTLObjectTransformGroups.get(ct).getTheOutline()
						.setVisible(true);
				MouseControlMode currMode = theView.getTheController().getSTLPreviewMouseController().getCurrentMouseControlMode();
				if(currMode==MouseControlMode.MODEL_ROTATE_Y){
					angleIndicators.get(ct).setVisible(true);
				} else if(currMode==MouseControlMode.SCALE){
					scaleIndicators.get(ct).setVisible(true);
				} else if(currMode==MouseControlMode.MODEL_TRANSLATE_XZ){
					positionIndicators.get(ct).setVisible(true);
				}

			} else {
				theSTLObjectTransformGroups.get(ct).getTheOutline()
						.setVisible(false);
				angleIndicators.get(ct).setVisible(false);
				scaleIndicators.get(ct).setVisible(false);
				positionIndicators.get(ct).setVisible(false);
			}
			ct++;
		}
		
		mostRecentPickRay.getClickPosInWorld().set(theRay.getClickPosInWorld());
		mostRecentPickRay.getDirection().set(theRay.getDirection());
		
		return thePick;
	}

	public void getPickingRay(float x, float y, PickingRay pickingRay) {

		// x = getMouseX() // scalar y = getMouseY() // scalar

		// view = cameraLookAt - cameraPosition // 3D float vector normalize
		// view

		Vector3f view = new Vector3f();
		view.set(posLookUp[3], posLookUp[4], posLookUp[5]);
		view.sub(new Vector3f(posLookUp[0], posLookUp[1], posLookUp[2]));
		view.normalize();

		Vector3f up = new Vector3f(0, 1, 0);
		// h = crossProduct( view, cameraUp ) // 3D float vector normalize h

		Vector3f h = new Vector3f();
		h.cross(view, up);
		h.normalize();

		// v = crossProduct( h, view) // 3D float vector normalize v

		Vector3f v = new Vector3f();
		v.cross(h, view);
		v.normalize();

		// // convert fovy to radians
		// rad = fovy * PI / 180
		// vLength = tan( rad / 2 ) * nearClippingPlaneDistance
		// hLength = vLength * (width / height)

		float rad = (float) (fovy * Math.PI / 180);
		float vLength = (float) Math.tan(rad / 2) * nearClippingPlaneDistance;
		float hLength = vLength * (width / height);

		// scale v by vLength scale h by hLength

		v.scale(vLength);
		h.scale(hLength);

		// // translate mouse coordinates so that the origin lies in the center
		// // of the view port x -= width / 2 y -= height / 2

		x -= (width / 2.0);
		y -= (height / 2.0);

		// // scale mouse coordinates so that half the view port width and
		// height // becomes 1
		// y /= (height / 2) x /= (width / 2)

		y /= (height / 2.0);
		x /= (width / 2.0);

		// // linear combination to compute intersection of picking ray with
		// view port plane
		// pos = cameraPos + view*nearClippingPlaneDistance + h*x + v*y

		Vector3f viewScaled = new Vector3f(view);
		viewScaled.scale(nearClippingPlaneDistance);

		Vector3f hScaled = new Vector3f(h);
		hScaled.scale(x);

		Vector3f vScaled = new Vector3f(v);
		vScaled.scale(y);

		Vector3f pos = new Vector3f(posLookUp[0], posLookUp[1], posLookUp[2]);
		pos.add(viewScaled);
		pos.add(hScaled);
		pos.add(vScaled);

		// // compute direction of picking ray by subtracting intersection point
		// // with camera position
		// dir = pos - cameraPos

		Vector3f dir = new Vector3f(pos);
		dir.sub(new Vector3f(posLookUp[0], posLookUp[1], posLookUp[2]));

		pickingRay.getClickPosInWorld().set(pos);
		pickingRay.getDirection().set(dir);

	}

	protected GLHandles getTheGLHandles() {
		return this.theGLHandles;
	}

}
